package com.thesis.manage.common.elasticsearch.impl;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.thesis.manage.common.constant.Table;
import com.thesis.manage.common.elasticsearch.ElasticsearchService;
import com.thesis.manage.common.entity.AbstractBaseEntity;
import com.thesis.manage.common.exception.CommonException;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Arrays;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;

/**
 * @author php
 * @date 2017/11/17
 * elasticsearch公共接口实现类
 */
public abstract class AbstractElasticsearchServiceImpl<T extends AbstractBaseEntity> implements ElasticsearchService<T> {

    private static final int MAX_COUNT = 10000;
    private static List<Class> CLASSES = new ArrayList<>();
    @Resource
    private TransportClient transportClient;
    @Value("${elasticsearch.database}")
    private String elasticsearchDatabase;

    protected AbstractElasticsearchServiceImpl() {
        this.getClasses();
    }


    private void getClasses() {
        if (getEntityClass() != AbstractBaseEntity.class) {
            CLASSES.add(getEntityClass());
            CLASSES.add(getEntityClass().getSuperclass());
        }
    }

    @Override
    public String save(T o) {
        IndexResponse response;
        try {
            response = this.transportClient.prepareIndex(elasticsearchDatabase, getTypeName(), o.getId().toString())
                    .setSource(getFields(o)).execute().actionGet();
        } catch (Exception e) {
            throw new CommonException("elasticsearch.save.failed", "elasticsearch保存对象失败");
        }
        return response.getId();
    }

    @Override
    public String update(T o) {
        if (null == o || o.getId() == null) {
            throw new CommonException("elasticsearch.update.id.null", "id不能为空");
        }
        try {
            XContentBuilder xContentBuilder = jsonBuilder();
            xContentBuilder.map(getFields(o));
            UpdateResponse updateResponse = this.transportClient.prepareUpdate(elasticsearchDatabase, getTypeName(), o.getId().toString())
                    .setDoc(xContentBuilder)
                    .get();
            return updateResponse.getId();
        } catch (IOException e) {
            throw new CommonException("update.failed", "修改对象失败");
        }
    }

    @Override
    public void deleteById(String id) {
        DeleteResponse deleteResponse = this.transportClient.prepareDelete(elasticsearchDatabase, getTypeName(), id).get();
        if (deleteResponse.getResult().getOp() != DocWriteResponse.Result.DELETED.getOp()) {
            throw new CommonException("elasticsearch.deleteById.failed", "elasticsearch删除数据失败");
        }
    }

    @Override
    public List<T> queryAll() {
        SearchHits hits = this.transportClient.prepareSearch(elasticsearchDatabase).setTypes(getTypeName()).get().getHits();
        if (null == hits.getHits() || hits.getHits().length < 1) {
            return null;
        }
        if (hits.getHits().length > MAX_COUNT) {
            throw new CommonException("elasticsearch.return.less.than or equals 10000", "一次不能超过10000条数据，可配置修改");
        }
        SearchResponse searchResponse = this.transportClient.prepareSearch(elasticsearchDatabase)
                .setTypes(getTypeName())
                .setQuery(QueryBuilders.matchAllQuery())
                .addSort(SortBuilders.fieldSort("startTime").order(SortOrder.ASC))
                .setFrom(0)
                .setSize(Integer.parseInt(hits.getTotalHits() + ""))
                .get();
        List<T> result = new ArrayList<>();
        for (SearchHit searchHit : searchResponse.getHits()) {
            try {
                T t = JSONObject.parseObject(searchHit.getSourceAsString(), getEntityClass());
                result.add(t);
            } catch (Exception e) {
                throw new CommonException("query.all.failed", "查询所有对象失败");
            }
        }
        return result;
    }

    @Override
    public T findById(String id) {
        T t = null;
        try {
            GetResponse response = this.transportClient.prepareGet(elasticsearchDatabase, getTypeName(), id)
                    //设置在同一个线程中执行
                    .setOperationThreaded(false)
                    .get();
            if (response.isExists()) {
                t = JSONObject.parseObject(response.getSourceAsString(), getEntityClass());
            }
        } catch (Exception e) {
            throw new CommonException("elasticsearch.findById.failed", "elasticsearch单条数据查询失败");
        }
        return t;
    }

    @Override
    public Page<T> queryByPage(Pageable pageable, String keyword) {
        SearchRequestBuilder requestBuilder = this.transportClient.prepareSearch(elasticsearchDatabase)
                .setTypes(getTypeName())
                .addSort(SortBuilders.fieldSort("startTime").order(SortOrder.ASC));
        if (null == pageable) {
            //默认从0-20开始
            requestBuilder.setFrom(0).setSize(20);
        } else {
            requestBuilder.setFrom((pageable.getPageNumber() - 1) * pageable.getPageSize())
                    .setSize(pageable.getPageSize());
        }
        if (!StringUtils.isEmpty(keyword)) {
            requestBuilder.setQuery(QueryBuilders.queryStringQuery(keyword));
        } else {
            requestBuilder.setQuery(QueryBuilders.matchAllQuery());
        }
        SearchResponse searchResponse = requestBuilder.get();
        List<T> result = new ArrayList<>();
        for (SearchHit searchHit : searchResponse.getHits()) {
            try {
                T t = JSONObject.parseObject(searchHit.getSourceAsString(), getEntityClass());
                result.add(t);
            } catch (Exception e) {
                throw new CommonException("query.ByPage.failed", "分页查询对象失败");
            }
        }
        return new PageImpl<>(result, pageable, searchResponse.getHits().totalHits);
    }

    @Override
    public Page<T> queryByPage(Pageable pageable, T t) {
        SearchRequestBuilder requestBuilder = this.transportClient.prepareSearch(elasticsearchDatabase)
                .setTypes(getTypeName())
                .addSort(SortBuilders.fieldSort("startTime").order(SortOrder.ASC));
        if (null == pageable) {
            //默认从0开始
            requestBuilder.setFrom(0).setSize(20);
        } else {
            requestBuilder.setFrom((pageable.getPageNumber() - 1) * pageable.getPageSize())
                    .setSize(pageable.getPageSize());
        }
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        try {
            for (Class clazz : CLASSES) {
                if (!Arrays.isNullOrEmpty(clazz.getDeclaredFields())) {
                    for (Field field : clazz.getDeclaredFields()) {
                        field.setAccessible(true);
                        final boolean isExist = field.isAnnotationPresent(JsonIgnore.class);
                        if (isExist) {
                            continue;
                        }
                        Object fieldValue = field.get(t);
                        if (null != fieldValue) {
                            boolQueryBuilder.must(QueryBuilders.matchPhraseQuery(field.getName(), fieldValue));
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new CommonException("object.cast.failed", "对象转换异常");
        }
        requestBuilder.setQuery(boolQueryBuilder);
        SearchResponse searchResponse = requestBuilder.get();
        List<T> result = new ArrayList<>();
        for (SearchHit searchHit : searchResponse.getHits()) {
            try {
                T entity = JSONObject.parseObject(searchHit.getSourceAsString(), getEntityClass());
                result.add(entity);
            } catch (Exception e) {
                throw new CommonException("query.ByPage.failed", "分页查询失败");
            }
        }
        return new PageImpl<>(result, pageable, searchResponse.getHits().totalHits);
    }

    /**
     * 获取类型
     *
     * @return class
     */
    abstract Class<T> getEntityClass();

    private String getTypeName() {
        final boolean isExist = getEntityClass().isAnnotationPresent(Table.class);
        String typeName = null;
        if (isExist) {
            Table table = getEntityClass().getAnnotation(Table.class);
            if (!StringUtils.isBlank(table.value())) {
                typeName = table.value();
            }
        } else {
            typeName = this.getEntityClass().getSimpleName().toLowerCase();
        }
        return typeName;
    }

    /**
     * php
     * 反射获取类的所有属性（包含父类）
     * 21:25 2017/11/25
     */
    private Map<String, Object> getFields(T t) {
        Map<String, Object> map = new HashMap<>(16);
        try {
            for (Class clazz : CLASSES) {
                if (!Arrays.isNullOrEmpty(clazz.getDeclaredFields())) {
                    for (Field field : clazz.getDeclaredFields()) {
                        field.setAccessible(true);
                        final boolean isExist = field.isAnnotationPresent(JsonIgnore.class);
                        if (isExist) {
                            continue;
                        }
                        map.put(field.getName(), field.get(t));
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new CommonException("object.cast.failed", "对象转换异常");
        }
        return map;
    }
}
